import asyncio
import logging
import sys
import threading
import time

import pandas as pd
from binance.um_futures import UMFutures
from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient

from . import BinanceStrategyTool

ws_client = UMFuturesWebsocketClient()
logging.info("启动：ws_client")
ws_client.start()

BinanceTradeAPIFactory_List = []
recvWindow = 60000


def renow_list_key_thread():
    logging.info("启动：renow_list_key_thread")
    while True:
        try:
            for var in BinanceTradeAPIFactory_List:
                var.renow()
            time.sleep(60 * 30)
        except:
            time.sleep(5)


threading.Thread(target=renow_list_key_thread).start()


class BinanceTradeAPIFactory(UMFutures):
    def __init__(self, UID: int, APIKey: str, SecretKey: str, symbol: str, asset_type: str, logger):
        super().__init__(APIKey, SecretKey)

        # 账户属性
        self.UID = UID
        self.APIKey = APIKey
        self.SecretKey = SecretKey

        self.symbol = symbol
        self.asset_type = asset_type
        # 估值与持仓
        self.asset_value = 0
        self.all_long_quantity = 0
        self.ave_long_price = 0
        self.all_short_quantity = 0
        self.ave_short_price = 0

        # 功能属性
        self.logger = logger
        self.stop = False
        self.wait_orderId = None
        self.lock_new_order = False
        BinanceTradeAPIFactory_List.append(self)

        try:
            self.listen_key = self.new_listen_key()["listenKey"]
            ws_client.user_data(listen_key=self.listen_key, callback=self.__message_handler_user_data, id=self.UID)
            self.update_quantity_price()
        except:
            ex_type, ex_val, ex_stack = sys.exc_info()
            self.logger.warning(ex_val)
            self.close()

    def update_quantity_price(self):
        data = self.account(recvWindow=recvWindow)
        positions = data.get("positions")
        for var in positions:
            if var.get("symbol") == self.symbol:
                if var.get("positionSide") == "LONG":
                    self.all_long_quantity = abs(float(var.get("positionAmt")))
                    self.ave_long_price = float(var.get("entryPrice"))
                if var.get("positionSide") == "SHORT":
                    self.all_short_quantity = abs(float(var.get("positionAmt")))
                    self.ave_short_price = float(var.get("entryPrice"))

        self.logger.info("【多仓】 数量：" + str(self.all_long_quantity) + "  均价：" + str(self.ave_long_price))
        self.logger.info("【空仓】 数量：" + str(self.all_short_quantity) + "  均价：" + str(self.ave_short_price))
        self._hold_update()

    def close(self):
        if not self.stop:
            self._close_before()
            try:
                self.close_listen_key(listenKey=self.listen_key)
            except:
                pass
            self.stop = True
            BinanceTradeAPIFactory_List.remove(self)

    def renow(self):
        if not self.stop:
            self.renew_listen_key(self.listen_key)

    def __message_handler_user_data(self, user_data):
        eventType = user_data.get('e')
        if eventType == "ACCOUNT_UPDATE":
            self.__ws_ACCOUNT_UPDATE(user_data)
        if eventType == "ORDER_TRADE_UPDATE":
            self.__ws_ORDER_TRADE_UPDATE(user_data)

    def __ws_ORDER_TRADE_UPDATE(self, order):
        openLong = (order.get('o').get('S') == "BUY" and order.get('o').get('ps') == "LONG")
        openShort = (order.get('o').get('S') == "SELL" and order.get('o').get('ps') == "SHORT")
        closeLong = (order.get('o').get('S') == "SELL" and order.get('o').get('ps') == "LONG")
        closeShort = (order.get('o').get('S') == "BUY" and order.get('o').get('ps') == "SHORT")

        if order.get("o").get("s") != self.symbol:
            return

        origQty = order['o']['q']
        executedQty = order['o']['z']

        if origQty != executedQty:
            return

        price = order.get("o").get("ap")
        date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(order.get('o').get('T')) / 1000))

        pl = float(order.get('o').get('rp')) - float(order.get('o').get('n'))
        if pl != 0:
            self.logger.info("交易撮合盈亏：" + str(pl))
            self._profit_loss(pl)

        if closeLong or closeShort:
            self._ws_closePosition(order)
            if closeLong:
                self.logger.info(
                    "【平多】 完成时间:" + date + " 仓位:" + executedQty + " 价格:" + price)
            elif closeShort:
                self.logger.info(
                    "【平空】 完成时间:" + date + " 仓位:" + executedQty + " 价格:" + price)

        if openLong or openShort:
            self.wait_orderId = int(order.get("o").get("i"))
            self._ws_openPosition(order)
            if openLong:
                self.logger.info(
                    "【开多】 完成时间:" + date + " 仓位:" + executedQty + " 价格:" + price)
            elif openShort:
                self.logger.info(
                    "【开空】 完成时间:" + date + " 仓位:" + executedQty + " 价格:" + price)

        self._ws_ORDER_TRADE_UPDATE(order)

    def __ws_ACCOUNT_UPDATE(self, user_data):
        if user_data.get("a").get("m") == "ORDER":
            asset = 0
            for var in user_data.get("a").get("B"):
                if var.get("a") == self.asset_type:
                    asset = float(var.get("wb"))
            for hold in user_data.get("a").get("P"):
                if hold.get("s") == self.symbol:
                    if hold.get("ps") == "LONG":
                        self.all_long_quantity = abs(float(hold.get("pa")))
                        self.ave_long_price = float(hold.get("ep"))
                    if hold.get("ps") == "SHORT":
                        self.all_short_quantity = abs(float(hold.get("pa")))
                        self.ave_short_price = float(hold.get("ep"))
                    asset = asset + float(hold.get("up"))
            self.logger.info(self.asset_type + ": " + str(asset))
            self.asset_value = asset
            self._asset_update(asset)
            self.logger.info("【多仓】 数量：" + str(self.all_long_quantity) + "  均价：" + str(self.ave_long_price))
            self.logger.info("【空仓】 数量：" + str(self.all_short_quantity) + "  均价：" + str(self.ave_short_price))
            self._hold_update()
            self._ws_ACCOUNT_UPDATE_ORDER(user_data)

        self._ws_ACCOUNT_UPDATE(user_data)

    def get_asset_value(self, asset):
        try:
            data = self.balance(recvWindow=recvWindow)
        except:
            ex_type, ex_val, ex_stack = sys.exc_info()
            self.logger.warning(ex_val)
            return 0
        all_asset = pd.json_normalize(data).drop("accountAlias", axis=1)
        balance = all_asset[all_asset["asset"] == asset]["balance"].values
        crossUnPnl = all_asset[all_asset["asset"] == asset]["crossUnPnl"].values
        value = float(balance) + float(crossUnPnl)
        self.asset_value = value
        self._asset_update(value)
        self.logger.info(asset + ": " + str(value))
        return value

    # ===================================================开平操作===============================================
    async def openLong(self, quantity):
        if self.lock_new_order:
            return False
        quantity = BinanceStrategyTool.set_accuracy(self.symbol, quantity)
        return await self.__http_API(side="BUY", positionSide="LONG", quantity=quantity)

    async def openShort(self, quantity):
        if self.lock_new_order:
            return False
        quantity = BinanceStrategyTool.set_accuracy(self.symbol, quantity)
        return await self.__http_API(side="SELL", positionSide="SHORT", quantity=quantity)

    async def closeLong(self, quantity):
        quantity = BinanceStrategyTool.set_accuracy(self.symbol, quantity)
        if await self.__http_API(side="SELL", positionSide="LONG", quantity=quantity):
            self.all_long_quantity -= float(quantity)  # 因平仓无需等待平仓完成，需提前计算剩余量
            if self.all_long_quantity <= 0:
                self.ave_long_price = 0
                self.all_long_quantity = 0
                self.lock_new_order = False
            return True
        return False

    async def closeShort(self, quantity):
        quantity = BinanceStrategyTool.set_accuracy(self.symbol, quantity)
        if await self.__http_API(side="BUY", positionSide="SHORT", quantity=quantity):
            self.all_short_quantity -= float(quantity)  # 因平仓无需等待平仓完成，需提前计算剩余量
            if self.all_short_quantity <= 0:
                self.ave_short_price = 0
                self.all_short_quantity = 0
                self.lock_new_order = False
            return True
        return False

    async def __http_API(self, side, positionSide, quantity):

        try:
            order = self.new_order(symbol=self.symbol, side=side, type="MARKET", positionSide=positionSide,
                                   quantity=quantity, newClientOrderId="x-r6h4Vhhh",
                                   recvWindow=recvWindow)

        except Exception:
            ex_type, ex_val, ex_stack = sys.exc_info()
            try:
                if ex_val.error_code == -2022:  # 无仓平仓 返回平仓成功
                    self.logger.error("平仓失败! 原因:不能仅减仓")
                    return True
                if ex_val.error_code == -4164:
                    self.logger.error("开仓失败! 原因:下单金额过低")
                    self.lock_new_order = True
                    return False
                if ex_val.error_code == -2015:
                    self.logger.error("下单失败！原因：APIKey不正确!")
                    await asyncio.sleep(60)
                    self.close()
                    return False
            except:
                pass
            self.logger.warning(ex_val)
            await asyncio.sleep(10)
            return False

        openLong = (side == "BUY" and positionSide == "LONG")
        openShort = (side == "SELL" and positionSide == "SHORT")
        if openLong or openShort:
            return await self.__wait_order(order)
        return True

    async def __wait_order(self, order):
        orderId = int(order.get("orderId"))
        for i in range(1, 600):
            await asyncio.sleep(0.1)
            if self.wait_orderId == orderId:
                self.wait_orderId = None
                return True
        return False

    # ======================================================================================================
    def _ws_ACCOUNT_UPDATE(self, user_data):
        pass

    def _ws_openPosition(self, order):
        pass

    def _ws_closePosition(self, order):
        pass

    def _close_before(self):
        pass

    def _ws_ORDER_TRADE_UPDATE(self, user_data):
        pass

    def _ws_ACCOUNT_UPDATE_ORDER(self, user_data):
        pass

    def _profit_loss(self, pl):
        pass

    def _error(self):
        pass

    def _hold_update(self):
        pass

    def _asset_update(self, asset):
        pass
